/*
 * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
 *
 * http://izpack.org/ http://izpack.codehaus.org/
 *
 * Copyright 2002 Elmar Grom
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.izforge.izpack.gui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.LayoutManager2;
import java.awt.Stroke;
import java.util.ArrayList;

/**
 * This class implements a layout manager that generally lays out components in two columns. <BR>
 * <BR>
 * The design goal for this layout manager was to lay out forms for data entry, where there are
 * several rows of entry fields with associated labels. The goal was to have the beginning off all
 * labels line up, as well as the left edge of all the data entry fields. This leads to a situation
 * where all components are essentially laid out in two columns. The columns adjust to accommodate
 * components of various sizes. This means that components that are added are laid out top to
 * bottom, either in the left column, in the right column or straddling both columns. In addition to
 * this general behavior, the following additional layout capabilities are supported:<br>
 * <ul>
 * <li>Resizable margins are provided on the left and right side.
 * <li>A special region is provided at the top that is only affected by the side margins but not by
 * any other layout behavior.
 * <li>It is possible to specify the vertical positioning of the cluster of laid out components for
 * the case that they do not occupy the entire available real estate.
 * <li>Individual components can be indented.
 * </ul>
 * <p/>
 * <b>The Layout Behavior</b> <br>
 * <br>
 * The left and right margin are absolute. This means that they can not be penetrated by components.
 * All layout happens between the limits established by these margins. The purpose of these margins
 * is to ensure that components are not laid out all the way to the edge of their container, without
 * the need to set matching borders for each component. <br>
 * <br>
 * The title margin at the top factors only into the layout behavior if there is a component set to
 * be laid out in that region, otherwise it is ignored. <br>
 * <br>
 * The vertical space available to each row of components depends on the space requirements of the
 * tallest component in that row. Both components are placed vertically centered in their row. <br>
 * <br>
 * All horizontal layout is based on the position of three vertical rules, the left rule, the right
 * rule and the center rule. <br>
 * <br>
 * <img src="doc-files/TwoColumnLayout.gif"/> <br>
 * <br>
 * The actual position of each rule depends on the alignment strategy, margin settings and component
 * sizes. Regardless of these factors, components placed in the left column are <i>always</i>
 * positioned with their left edge aligned with the left rule. Components placed in the right column
 * are <i>always</i> positioned with their left edge aligned with the center rule. If a component
 * straddles both columns, it is <i>always</i> positioned with the left edge aligned with the left
 * rule, but is allowed to extend all the way to the right rule. The only exception is a component
 * that is specified with an indent. In this case the component is moved to the right of the
 * respective rule by the indent amount. <br>
 * <br>
 * The location of the rules is determined based on the alignment strategy as follows:<br>
 * <ul>
 * <li>The right rule is always located at the edge of the right margin.
 * <li><b>Left Alignment:</b> The left rule is located the edge of the left margin. The center rule
 * is located far enough to the right to clear the widest component in the left column.
 * <li><b>Center Alignment:</b> The center rule is located at the center of the panel. The left rule
 * is located far enough to the left to make the widest component in the left column fit.
 * <li><b>Right Alignment</b> The center rule is located far enough to the left of the right rule to
 * make the widest component in the right column fit. The left rule is located far enough to the
 * left to make the widest component in the left column fit.
 * </ul>
 * All components clump together vertically and are positioned right beneath the title margin. This
 * is of course not a very appealing presentation. By setting how the remaining vertical space is
 * distributed above and below the cluster of components the cluster can be positioned more
 * favorably (see the shaded area in the illustration).
 *
 * @author Elmar Grom
 * @version 0.0.1 / 11/14/02
 * @see com.izforge.izpack.gui.TwoColumnConstraints
 */
public class TwoColumnLayout implements LayoutManager2
{

    public static final int LEFT = 0;

    public static final int RIGHT = 1;

    public static final int CENTER = 2;

    /**
     * holds all the components and layout constraints.
     */
    @SuppressWarnings("unchecked")
    private java.util.List<TwoColumnConstraints>[] components = new ArrayList[]{new ArrayList<TwoColumnConstraints>(),
            new ArrayList<TwoColumnConstraints>()};

    /**
     * holds the component to be placed in the title region, including layout constraints.
     */
    private TwoColumnConstraints title = null;

    /**
     * the margin setting in % of the container's width
     */
    private int margin = 0;

    /**
     * the setting for the buffer area on top of the component cluster in % of the left over height.
     * if rigid is set to true, the setting for this will be the setting for the buffer area on top in terms of pixels
     */
    private int topBuffer = 0;

    /**
     * the setting for the buffer area on top of the component to be used in pixels if true
     * this setting is false by default
     */
    private boolean rigid = false;

    /**
     * the indent setting in % of the conteiner's width
     */
    private int indent = 0;

    /**
     * the gap between the two columns
     */
    private int gap = 5;

    private int colWidth;

    private int alignment = LEFT;

    private int leftRule;

    private int rightRule;

    private int centerRule;

    private int titleHeight;

    /**
     * Constructs a <code>TwoColumnLayout</code> layout manager. To add components use the
     * container's <code>add(comp, constraints)</code> method with a TwoColumnConstraints object.
     *
     * @param margin    the margin width to use on the left and right side in % of the total container
     *                  width. Values less than 0% and greater than 50% are not accepted.
     * @param gap       the gap between the two columns.
     * @param indent    the indent to use for components that have that constraint set. This is a value
     *                  in pixels.
     * @param topBuffer the percentage of left over vertical space to place on top of the component
     *                  cluster. Values between 0% and 100% are accepted.
     * @param colWidth  with of the left column in percent of the whole panel. Set to 0 for automatic
     *                  width.
     * @param alignment how to align the overall layout. Legal values are LEFT, CENTER, RIGHT.
     */
    public TwoColumnLayout(int margin, int gap, int indent, int topBuffer, boolean rigid, int colWidth, int alignment)
    {
        this.indent = indent;
        this.gap = gap;
        this.colWidth = colWidth;
        this.rigid = rigid;

        if ((margin >= 0) && (margin <= 50))
        {
            this.margin = margin;
        }
        if ((topBuffer >= 0) && (topBuffer <= 100) || rigid)
        {
            this.topBuffer = topBuffer;
        }
        if ((alignment == LEFT) || (alignment == CENTER) || (alignment == RIGHT))
        {
            this.alignment = alignment;
        }
    }

    /**
     * Constructs a <code>TwoColumnLayout</code> layout manager. To add components use the
     * container's <code>add(comp, constraints)</code> method with a TwoColumnConstraints object.
     *
     * @param margin    the margin width to use on the left and right side in % of the total container
     *                  width. Values less than 0% and greater than 50% are not accepted.
     * @param gap       the gap between the two columns.
     * @param indent    the indent to use for components that have that constraint set. This is a value
     *                  in pixels.
     * @param topBuffer the percentage of left over vertical space to place on top of the component
     *                  cluster. Values between 0% and 100% are accepted.
     * @param alignment how to align the overall layout. Legal values are LEFT, CENTER, RIGHT.
     */
    public TwoColumnLayout(int margin, int gap, int indent, int topBuffer, boolean rigid, int alignment)
    {
        this(margin, gap, indent, topBuffer, rigid, 0, alignment);
    }

    /**
     * Sets the constraints for the specified component in this layout. <code>null</code> is a legal
     * value for a component, but not for a constraints object.
     *
     * @param comp        the component to be modified.
     * @param constraints the constraints to be applied.
     */
    @Override
    public void addLayoutComponent(Component comp, Object constraints)
    {
        if (constraints == null)
        {
            return;
        }

        if (!comp.isVisible())
        {
            // Don't leave spave for invisible components, like radiobutton choices
            return;
        }

        TwoColumnConstraints component = null;
        try
        {
            component = (TwoColumnConstraints) constraints;
            component = (TwoColumnConstraints) component.clone();
        }
        catch (Throwable exception)
        {
            return;
        }

        component.component = comp;

        // ----------------------------------------------------
        // the title component is recorded in a separate
        // variable, displacing any component that might have
        // been previously recorded for that location.
        // ----------------------------------------------------
        if (component.position == TwoColumnConstraints.NORTH)
        {
            title = component;
            if (title.stretch)
            {
                title.align = LEFT;
            }
        }

        // ----------------------------------------------------
        // components that straddle both columns are a bit
        // tricky because these components are recorded in the
        // left column and the same row cannot contain a
        // component in the right column.
        //
        // If there are fewer components in the left column
        // than in the right one, a null is inserted at this
        // place in the right column. This allows the component
        // to use both columns. The component that previously
        // occupied this position and any that were placed
        // below will be pushed down by one row due to this
        // action.
        //
        // If there are the same number of components in both
        // columns or if there are fewer in the right column
        // then the component is added to the left column and
        // then the right column filled with null until both
        // contain the same number of components. this means
        // that any components that will now be placed in the
        // right column are positioned beneath this component.
        // Unoccupied spots higher in the right column become
        // inaccessible.
        // ----------------------------------------------------
        else if (component.position == TwoColumnConstraints.BOTH)
        {
            // first make sure that both columns have the same number of entries
            while (components[RIGHT].size() > components[LEFT].size())
            {
                components[LEFT].add(null);
            }

            while (components[LEFT].size() > components[RIGHT].size())
            {
                components[RIGHT].add(null);
            }

            components[LEFT].add(component);
            components[RIGHT].add(null);
        }

        // ----------------------------------------------------
        // WEST components are added to the left column
        // ----------------------------------------------------
        else if (component.position == TwoColumnConstraints.WEST)
        {
            components[LEFT].add(component);
        }

        // ----------------------------------------------------
        // WESTONLY components are added to the left column
        // the right column has to be kept free
        // ----------------------------------------------------
        else if (component.position == TwoColumnConstraints.WESTONLY)
        {
            components[LEFT].add(component);

            // fill right column to make sure nothing is placed there
            while (components[RIGHT].size() < components[LEFT].size())
            {
                components[RIGHT].add(null);
            }

        }

        // ----------------------------------------------------
        // EAST components are added to the right column
        // ----------------------------------------------------
        else if (component.position == TwoColumnConstraints.EAST)
        {
            components[RIGHT].add(component);
        }

        // ----------------------------------------------------
        // EASTONLY components are added to the left column
        // the right column has to be kept free
        // ----------------------------------------------------
        else if (component.position == TwoColumnConstraints.EASTONLY)
        {
            components[RIGHT].add(component);

            // fill left column to make sure nothing is placed there
            while (components[LEFT].size() < components[RIGHT].size())
            {
                components[LEFT].add(null);
            }

        }

        // ----------------------------------------------------
        // If the position did not match any of the above
        // criteria then the component is not added and
        // consequently will not be laid out.
        // ----------------------------------------------------
    }

    /**
     * Lays out the container in the specified panel.
     *
     * @param parent the component which needs to be laid out.
     */
    @Override
    public void layoutContainer(Container parent)
    {
        DimensionExtractor dimensionExtractor = DimensionExtractor.PREFERRED;
        positionRules(parent, dimensionExtractor);
        positionTitle(parent, dimensionExtractor);
        positionComponents(parent, dimensionExtractor);
    }

    /**
     * Positions the three rules in preparation for layout. Sets the variables:<br>
     * <ul>
     * <li><code>leftRule</code>
     * <li><code>rightRule</code>
     * <li><code>centerRule</code>
     * </ul>
     *
     * @param parent the component which needs to be laid out.
     * @param dimensionExtractor the extractor of the correct size
     */
    private void positionRules(Container parent, DimensionExtractor dimensionExtractor)
    {
        int margin = margin(parent);

        if (alignment == LEFT)
        {
            leftRule = margin;
            rightRule = parent.getWidth() - margin;
            if (colWidth > 0)
            {
                centerRule = leftRule + (rightRule - leftRule) / 100 * colWidth + gap;
            }
            else
            {
                centerRule = leftRule + columnWidth(LEFT, parent, dimensionExtractor) + gap;
            }
        }
        else if (alignment == CENTER)
        {
            centerRule = (int) (parent.getMinimumSize().getWidth() / 2);
            leftRule = centerRule - columnWidth(LEFT, parent, dimensionExtractor) - gap;
            rightRule = parent.getWidth() - margin;
        }
        else if (alignment == RIGHT)
        {
            rightRule = parent.getWidth() - margin;
            leftRule = centerRule - columnWidth(LEFT, parent, dimensionExtractor) - gap;
            if (colWidth > 0)
            {
                centerRule = rightRule - (rightRule - leftRule) / 100 * colWidth;
            }
            else
            {
                centerRule = rightRule - columnWidth(RIGHT, parent, dimensionExtractor);
            }
        }
    }

    /**
     * Positions the title component and sets the variable <code>titleHeight</code>. <b>Note:</b>
     * this method depends on the fact that the rules are set to their correct layout position.
     *
     * @param parent the component which needs to be laid out.
     * @param dimensionExtractor the extractor of the correct size
     */
    private void positionTitle(Container parent, DimensionExtractor dimensionExtractor)
    {
        if (title != null)
        {
            Component component = title.component;
            Dimension dim = dimensionExtractor.apply(component);
            int width = dim.width;
            titleHeight = dim.height;

            if (component != null)
            {
                if (title.stretch)
                {
                    width = rightRule - leftRule;
                    component.setBounds(leftRule, 0, width, titleHeight);
                }

                else if (title.align == TwoColumnConstraints.LEFT)
                {
                    component.setBounds(leftRule, 0, width, titleHeight);
                }

                else if (title.align == TwoColumnConstraints.CENTER)
                {
                    int left = centerRule - (width / 2);
                    component.setBounds(left, 0, width, titleHeight);
                }

                else if (title.align == TwoColumnConstraints.RIGHT)
                {
                    int left = rightRule - width;
                    component.setBounds(left, 0, width, titleHeight);
                }
            }
        }
    }

    /**
     * Positions all components in the container.
     *
     * @param parent the component which needs to be laid out.
     * @param dimensionExtractor the extractor of the correct size
     */
    private void positionComponents(Container parent, DimensionExtractor dimensionExtractor)
    {
        int usedHeight = titleHeight + clusterHeight(dimensionExtractor);
        int topBuffer = topBuffer(usedHeight, parent);
        int leftHeight = 0;
        int rightHeight = 0;

        if (topBuffer < 0)
        {
            topBuffer = 0;
        }

        int y = titleHeight + topBuffer;

        for (int i = 0; i < rows(); i++)
        {
            leftHeight = height(i, LEFT, dimensionExtractor);
            rightHeight = height(i, RIGHT, dimensionExtractor);

            if (leftHeight > rightHeight)
            {
                int offset = (leftHeight - rightHeight) / 2;

                positionComponent(y, i, LEFT, parent);
                positionComponent((y + offset), i, RIGHT, parent);

                y += leftHeight;
            }
            else if (leftHeight < rightHeight)
            {
                int offset = (rightHeight - leftHeight) / 2;

                positionComponent((y + offset), i, LEFT, parent);
                positionComponent(y, i, RIGHT, parent);

                y += rightHeight;
            }
            else
            {
                positionComponent(y, i, LEFT, parent);
                positionComponent(y, i, RIGHT, parent);

                y += leftHeight;
            }
        }
    }

    /**
     * Positions one component as instructed. Constraints for each component, such as
     * <code>stretch</code>, <code>BOTH</code> and <code>indent</code> are taken into account. In
     * addition, empty components are handled properly.
     *
     * @param y      the y location within the container, where the component should be positioned.
     * @param row    the row of the component
     * @param column the column of the component
     * @param parent the container which needs to be laid out.
     */
    private void positionComponent(int y, int row, int column, Container parent)
    {
        TwoColumnConstraints constraints = null;

        try
        {
            constraints = components[column].get(row);
        }
        catch (Throwable exception)
        {
            return;
        }

        int x = 0;

        if (constraints != null)
        {
            Component component = constraints.component;
            int width = (int) component.getPreferredSize().getWidth();
            int height = (int) component.getPreferredSize().getHeight();

            // --------------------------------------------------
            // set x to the appropriate rule. The only need to
            // modify this is for indent
            // --------------------------------------------------
            boolean stretchBool = constraints.stretch;
            boolean indentBool = constraints.indent;
            int align = constraints.align;
            if (constraints.position == TwoColumnConstraints.BOTH)
            {
                width = getWidth(leftRule, rightRule, stretchBool, indentBool, width);
                if (width > (rightRule - leftRule))
                {
                    stretchBool = true;
                }
                x = getPosition(leftRule, rightRule, stretchBool, indentBool, width, align);
            }
            else if (column == LEFT)
            {
                width = getWidth(leftRule, centerRule, stretchBool, indentBool, width);
                x = getPosition(leftRule, centerRule, stretchBool, indentBool, width, align);
            }
            else
            {
                width = getWidth(centerRule, rightRule, stretchBool, indentBool, width);
                x = getPosition(centerRule, rightRule, stretchBool, indentBool, width, align);
            }

            if (component != null)
            {
                component.setBounds(x, y, width, height);
            }
        }
    }

    private int getWidth(int left, int right, boolean stretch, boolean indent, int componentWidth)
    {
        int width = componentWidth;
        // --------------------------------------------------
        // set the width for stretch based on BOTH, LEFT and
        // RIGHT positioning
        // --------------------------------------------------
        if (stretch)
        {
            width = right - left;
        }

        // --------------------------------------------------
        // correct for indent if this option is set
        // --------------------------------------------------
        if (indent)
        {
            width -= this.indent;
        }
        return width;
    }

    private int getPosition(int left, int right, boolean stretch, boolean indent, int componentWidth, int align)
    {
        int position = left;

        // --------------------------------------------------
        // Correct position with alignment parameters
        // --------------------------------------------------
        if (align == TwoColumnConstraints.LEFT)
        {
            position = left;
        }
        else if (align == TwoColumnConstraints.CENTER)
        {
            position += (right - left) / 2 - componentWidth / 2 - this.gap / 2;
        }
        else if (align == TwoColumnConstraints.RIGHT)
        {
            position = right - componentWidth - this.gap;
        }

        // --------------------------------------------------
        // correct for indent if this option is set
        // --------------------------------------------------
        if (indent)
        {
            position += this.indent;
        }
        return position;
    }

    /**
     * Returns the minimum width of the column requested.
     *
     * @param column the columns to measure (LEFT / RIGHT)
     * @param parent the component which needs to be laid out.
     * @param dimensionExtractor the extractor of the correct size
     * @return the minimum width required to fits the components in this column
     */
    private int columnWidth(int column, Container parent, DimensionExtractor dimensionExtractor)
    {
        Component component = null;
        int width = 0;
        int temp = 0;

        for (TwoColumnConstraints constraints : components[column])
        {
            if ((constraints != null) && (constraints.position != TwoColumnConstraints.BOTH))
            {
                component = constraints.component;
                temp = (int) component.getMinimumSize().getWidth();

                if (constraints.indent)
                {
                    temp += indent;
                }

                if (temp > width)
                {
                    width = temp;
                }
            }
        }

        return (width);
    }

    /**
     * Returns the minimum width both columns together should have based on the minimum widths of
     * all the components that straddle both columns and the minimum width of the title component.
     *
     * @param parent the component which needs to be laid out.
     * @param dimensionExtractor the extractor of the correct size
     * @return the minimum width required to fis the components in this column
     */
    private int bothColumnsWidth(Container parent, DimensionExtractor dimensionExtractor)
    {
        Component component = null;
        //TwoColumnConstraints constraints = null;
        int width = 0;
        int temp = 0;

        if (title != null)
        {
            component = title.component;
            Dimension dim = dimensionExtractor.apply(component);
            width = (int) dim.getWidth();
        }

        for (TwoColumnConstraints constraints : components[LEFT])
        {
            if ((constraints != null) && (constraints.position == TwoColumnConstraints.BOTH))
            {
                component = constraints.component;
                Dimension dim = dimensionExtractor.apply(component);
                temp = (int) dim.getWidth();

                if (constraints.indent)
                {
                    temp += indent;
                }

                if (temp > width)
                {
                    width = temp;
                }
            }
        }

        return (width);
    }

    private int clusterHeight(DimensionExtractor dimensionExtractor)
    {
        int height = 0;

        for (int i = 0; i < rows(); i++)
        {
            height += rowHeight(i, dimensionExtractor);
        }

        return (height);
    }

    /**
     * Returns the number of rows that need to be laid out.
     */
    private int rows()
    {
        int rows = 0;
        int leftRows = components[LEFT].size();
        int rightRows = components[RIGHT].size();

        if (leftRows > rightRows)
        {
            rows = leftRows;
        }
        else
        {
            rows = rightRows;
        }

        return (rows);
    }

    /**
     * Measures and returns the minimum height required to render the components in the indicated
     * row.
     *
     * @param row the index of the row to measure
     * @param dimensionExtractor the extractor of the correct size
     */
    private int rowHeight(int row, DimensionExtractor dimensionExtractor)
    {
        int height1 = height(row, LEFT, dimensionExtractor);
        int height2 = height(row, RIGHT, dimensionExtractor);

        // ----------------------------------------------------
        // take the higher one
        // ----------------------------------------------------

        int height = 0;
        height = Math.max(height, height1);
        height = Math.max(height, height2);

        return height;
    }

    /**
     * Measures and returns the minimum height required to render the component in the indicated row
     * and column.
     *
     * @param row    the index of the row to measure
     * @param column the column of the component to measure (<code>LEFT</code> or <code>RIGHT</code>)
     * @param dimensionExtractor the extractor of the correct size
     */
    private int height(int row, int column, DimensionExtractor dimensionExtractor)
    {
        int height = 0;
        int width = 0;
        Component component;
        TwoColumnConstraints constraints;

        try
        {
            constraints = components[column].get(row);
            if (constraints != null)
            {
                component = constraints.component;
                Dimension dim = dimensionExtractor.apply(component);
                width = Math.max(width,dim.width);
                height = Math.max(height, dim.height);

                if (constraints.position == TwoColumnConstraints.WEST)
                {
                    if (width > (centerRule - leftRule))
                    {
                        component.setBounds(0, 0, (centerRule - leftRule), height);
                    }
                }
                else if (constraints.position == TwoColumnConstraints.EAST)
                {
                    if (width > (rightRule - centerRule))
                    {
                        component.setBounds(0, 0, (rightRule - centerRule), height);
                    }
                }
                else if (constraints.position == TwoColumnConstraints.BOTH)
                {
                    if (width > (rightRule - leftRule))
                    {
                        component.setBounds(0, 0, (rightRule - leftRule), height);
                    }
                }

                return height;
            }
        }
        // ----------------------------------------------------
        // we might get an exception if one of the vectors is
        // shorter, because we index out of bounds. If there
        // is nothing there then the height is 0, nothing
        // further to worry about!
        // ----------------------------------------------------
        catch (Throwable exception)
        {
        }

        return (height);
    }

    /**
     * Computes the margin value based on the container width and the margin setting.
     *
     * @param parent the component which needs to be laid out.
     */
    private int margin(Container parent)
    {
        int amount = (int) (((parent.getSize().getWidth()) * margin) / 100);

        return (amount);
    }

    /**
     * Computes the top buffer value based on the container width and the setting for the top buffer
     * If rigid is set to true simply return the number of pixels to use as a top buffer indicated by the topBuffer variable
     *
     * @param usedHeight the amount of the parent component's height that is already in use (height
     *                   of the title and the combined height of all rows).
     * @param parent     the component which needs to be laid out.
     */
    private int topBuffer(int usedHeight, Container parent)
    {
        if (rigid)
        {
            return topBuffer;
        }
        else
        {
            int amount = ((int) parent.getSize().getHeight()) - usedHeight;
            amount = amount * topBuffer / 100;
            return (amount);
        }
    }

    /*--------------------------------------------------------------------------*/
    /**
     * Computes the indent value based on the container width and the indent setting.
     *
     * @param parent the component which needs to be laid out.
     */
    /*--------------------------------------------------------------------------*/
    /*
     * private int indent (Container parent) { int amount = (int)(((parent.getMinimumSize
     * ().getWidth ()) * indent) / 100);
     *
     * return (amount); }
     */

    /**
     * Calculates the preferred size dimensions for the specified panel given the components in the
     * specified parent container.
     *
     * @param parent the component to be laid out
     */
    @Override
    public Dimension preferredLayoutSize(Container parent)
    {
        DimensionExtractor dimensionExtractor = DimensionExtractor.PREFERRED;
        positionTitle(parent, dimensionExtractor);
        int width = bothColumnsWidth(parent, dimensionExtractor);
        int height = clusterHeight(dimensionExtractor) + titleHeight;

        if (rigid)
        {
            height = height + topBuffer;
        }

        return (new Dimension(width, height));
    }

    /**
     * Calculates the minimum size dimensions for the specified panel given the components in the
     * specified parent container.
     *
     * @param parent the component to be laid out
     */
    @Override
    public Dimension minimumLayoutSize(Container parent)
    {
        DimensionExtractor dimensionExtractor = DimensionExtractor.MINIMUM;
        positionTitle(parent, dimensionExtractor);
        int width = bothColumnsWidth(parent, dimensionExtractor);
        int height = clusterHeight(dimensionExtractor) + titleHeight;

        if (rigid)
        {
            height = height + topBuffer;
        }

        return (new Dimension(width, height));
    }

    /**
     * Calculates the maximum size dimensions for the specified panel given the components in the
     * specified parent container.
     *
     * @param parent the component to be laid out
     */
    @Override
    public Dimension maximumLayoutSize(Container parent)
    {
        return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
    }

    /**
     * Returns the alignment along the x axis. This specifies how the component would like to be
     * aligned relative to other components. The value should be a number between 0 and 1 where 0
     * represents alignment along the origin, 1 is aligned the furthest away from the origin, 0.5 is
     * centered, etc.
     *
     * @param parent the component to be laid out
     */
    @Override
    public float getLayoutAlignmentX(Container parent)
    {
        return (0);
    }

    /**
     * Returns the alignment along the y axis. This specifies how the component would like to be
     * aligned relative to other components. The value should be a number between 0 and 1 where 0
     * represents alignment along the origin, 1 is aligned the furthest away from the origin, 0.5 is
     * centered, etc.
     *
     * @param parent the component to be laid out
     */
    @Override
    public float getLayoutAlignmentY(Container parent)
    {
        return (0);
    }

    /**
     * Invalidates the layout, indicating that if the layout manager has cached information it
     * should be discarded.
     *
     * @param parent the component to be laid out
     */
    @Override
    public void invalidateLayout(Container parent)
    {
        leftRule = 0;
        rightRule = 0;
        centerRule = 0;
        titleHeight = 0;
    }

    /**
     * Adds the specified component with the specified name to the layout. This version is not
     * supported, use <code>addLayoutComponent</code> with layout contsraints.
     *
     * @param name the component name
     * @param comp the component to be added
     */
    @Override
    public void addLayoutComponent(String name, Component comp)
    {
    }

    /**
     * This functionality removes the TwoColumnConstraints from Vectors so that alignment of
     * components on UserInputPanel doesn't get dirty
     *
     * @param comp the component to be removed
     */
    @Override
    public void removeLayoutComponent(Component comp)
    {
        java.util.List<TwoColumnConstraints> left = components[LEFT];
        java.util.List<TwoColumnConstraints> right = components[RIGHT];

        for (int i = 0; i < left.size(); i++)
        {
            TwoColumnConstraints constraints = left.get(i);
            if (constraints == null)
            {
                continue;
            }
            Component ctemp = constraints.component;
            if (ctemp != null && ctemp.equals(comp))
            {
                if (constraints.position == TwoColumnConstraints.BOTH
                        || constraints.position == TwoColumnConstraints.WESTONLY)
                {
                    right.remove(i);
                }
                break;
            }
        }

        for (int j = 0; j < right.size(); j++)
        {
            TwoColumnConstraints constraints = right.get(j);
            if (constraints == null)
            {
                continue;
            }
            Component ctemp = constraints.component;
            if (ctemp != null && ctemp.equals(comp))
            {
                if (constraints.position == TwoColumnConstraints.BOTH
                        || constraints.position == TwoColumnConstraints.EASTONLY)
                {
                    left.remove(j);
                }
                break;
            }
        }
    }

    /**
     * This method is provided for conveninence of debugging layout problems. It renders the three
     * rules and the limit of the title marign visible after these positions have been computed. In
     * addition, the indent locations are shown as dashed lines. To use this functionality do the
     * following:<br>
     * <br>
     * <ul>
     * <li>in the container using this layout manager override the <code>paint()</code> method.
     * <li>in that method, first call <code>super.paint()</code>
     * <li>then call this method
     * </ul>
     * <br>
     * <b>Note:</b> cast the graphics object received in the <code>paint()</code> method to
     * <code>Graphics2D</code> when making the call.<br>
     * <br>
     *
     * @param graphics the graphics context used for drawing.
     * @param color    the color to use for rendering the layout grid
     */
    public void showRules(Graphics2D graphics, Color color)
    {
        int height = graphics.getClipBounds().height;

        Stroke currentStroke = graphics.getStroke();
        Color currentColor = graphics.getColor();

        Stroke stroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.5f, new float[]{10, 5}, 5);
        graphics.setColor(color);

        graphics.drawLine(leftRule, 0, leftRule, height);
        graphics.drawLine(centerRule, titleHeight, centerRule, height);
        graphics.drawLine(rightRule, 0, rightRule, height);
        graphics.drawLine(leftRule, titleHeight, rightRule, titleHeight);

        graphics.setStroke(stroke);
        graphics.drawLine((leftRule + indent), titleHeight, (leftRule + indent), height);
        graphics.drawLine((centerRule + indent), titleHeight, (centerRule + indent), height);

        graphics.setStroke(currentStroke);
        graphics.setColor(currentColor);
    }

    private enum DimensionExtractor {
        MINIMUM {
            @Override
            public Dimension apply(Component component) {
                return component.getMinimumSize();
            }
        }, PREFERRED {
            @Override
            public Dimension apply(Component component) {
                return component.getPreferredSize();
            }
        };
        public abstract Dimension apply(Component component);
    }

}
/*---------------------------------------------------------------------------*/
